﻿using System;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;

namespace NewScene
{
    public class SpaceBoxManager
    {
        public static SpaceBoxManager Inst { get; private set; }

        public static void Init()
        {
            if (Inst != null)
                return;
            Inst = new SpaceBoxManager();
        }
        
        private SpaceBoxManager(){}
        
        private static int MaxBoxCount => SpaceBox.SpaceBoxEndX * SpaceBox.SpaceBoxEndZ;
        public NativeList<SpaceBox> GameSpaceBoxes = new NativeList<SpaceBox>(MaxBoxCount, Allocator.Persistent);

        public NativeList<Vector2Int> GameSpaceBoxCenters =
            new NativeList<Vector2Int>(MaxBoxCount, Allocator.Persistent);

        private NativeQueue<SpaceLodBox> _loadAll = new NativeQueue<SpaceLodBox>(Allocator.Persistent);
        private NativeQueue<SpaceLodBox> _unLoadAll = new NativeQueue<SpaceLodBox>(Allocator.Persistent);
        private NativeList<SpaceBox> _updateAll = new NativeList<SpaceBox>(MaxBoxCount, Allocator.Persistent);

        private NativeParallelMultiHashMap<int, int> _spaceAssetCountMap =
            new NativeParallelMultiHashMap<int, int>(1000, Allocator.Persistent);

        private NativeList<SpaceAssetLoadRef> _spaceAssetMap =
            new NativeList<SpaceAssetLoadRef>(1000, Allocator.Persistent);

        private JobHandle _lodJobHandle;


        public void Update(float x, float z)
        {
            var sx = (int)(x / SpaceBox.SpaceBoxSize);
            var sz = (int)(z / SpaceBox.SpaceBoxSize);

            _loadAll.Clear();
            _unLoadAll.Clear();
            _updateAll.Clear();

            //计算哪些需要加载，哪些需要卸载，以及更新标识
            var handle = ScheduleProcessGrid(sx, sz, ref _loadAll, ref _unLoadAll, ref _updateAll);
            handle.Complete();

            var output = new NativeQueue<SpaceLodBox>(Allocator.Temp);
            var allLoads = _loadAll.ToArray(Allocator.Temp);
            ScheduleProcessBounds(ref allLoads, ref output); //计算视锥 => 视锥内物体权重100

            _spaceAssetCountMap.Clear();
            var lodTask = output.ToArray(Allocator.Temp);
            ScheduleProcessLod(ref lodTask, ref _spaceAssetCountMap); //计算LOD =》 对应哪些物体需要被加载

            Merge(); // => 统计出所有需要被加载的物体信息：加载权重，加载次数

            Load();

            UnLoad();

            output.Dispose();
            lodTask.Dispose();
            allLoads.Dispose();

            //写入加载优先级队列
        }

        private JobHandle ScheduleProcessLod(ref NativeArray<SpaceLodBox> boxes,
            ref NativeParallelMultiHashMap<int, int> outputList)
        {
            var job = new ProcessLodJob
            {
                SpaceBoxes = boxes,
                OutputKeys = outputList.AsParallelWriter()
            };

            JobHandle handle = job.Schedule(boxes.Length, 50);

            return handle;
        }

        private JobHandle ScheduleProcessGrid(int x, int z, ref NativeQueue<SpaceLodBox> outputLoads,
            ref NativeQueue<SpaceLodBox> outputUnloads, ref NativeList<SpaceBox> outputUpdates)
        {
            /*
             * 标记所有的空间，哪些需要加载，哪些需要卸载，加载的是哪个lod 卸载哪个lod
             */
            var job = new ProcessGridJob()
            {
                Positions = GameSpaceBoxCenters.AsReadOnly(),
                rect0 = SpaceBox.GetSpaceLodBox(0, x, z),
                rect1 = SpaceBox.GetSpaceLodBox(1, x, z),
                rect2 = SpaceBox.GetSpaceLodBox(2, x, z),
                spaceBoxEndX = SpaceBox.SpaceBoxEndX,

                OutputBoxesUpdate = outputUpdates,
                OutputBoxesLoad = outputLoads,
                OutputBoxesUnLoad = outputUnloads,
            };

            var handle = job.Schedule(GameSpaceBoxCenters.Length, 3000);
            return handle;
        }

        private JobHandle ScheduleProcessBounds(ref NativeArray<SpaceLodBox> boxes,
            ref NativeQueue<SpaceLodBox> outputList)
        {
            /*
             * 标记所有需要加载的空间，哪些是在视锥范围内
             */
            var job = new ProcessViewBoundJob
            {
                SpaceBoxes = boxes,
                OutputQueue = outputList
            };

            var handle = job.Schedule(boxes.Length, 100);
            return handle;
        }

        private void Merge()
        {
            _spaceAssetMap.Clear();

            var keys = _spaceAssetCountMap.GetKeyArray(Allocator.Temp);
            foreach (var item in keys) //key对应每个资源索引
            {
                if (!_spaceAssetCountMap.TryGetFirstValue(item, out var _, out var itr))
                    continue;
                int lod = int.MaxValue;
                int count = 0;
                //分别提取出值为0的个数，值为1的个数，值为2的个数
                bool inView = false;
                while (_spaceAssetCountMap.TryGetNextValue(out var v, ref itr))
                {
                    v = v % 10000;
                    inView |= v / 10000 == 1;
                    if (v < lod)
                    {
                        lod = v;
                        count = 1;
                        continue;
                    }

                    if (v == lod)
                    {
                        count++;
                        continue;
                    }
                }

                _spaceAssetMap.Add(new SpaceAssetLoadRef()
                {
                    RefCount = count,
                    SceneObjIndex = item,
                    LodIndex = lod,
                    Weight = inView ? 100 : 0
                });
            }
        }

        private void Load()
        {
            foreach (var item in _spaceAssetMap)
            {
                LoadManager.Inst.MarkLoad(item);
            }
        }

        private void UnLoad()
        {

        }

        [BurstCompile]
        public struct ProcessGridJob : IJobParallelFor
        {
            [ReadOnly] public NativeArray<Vector2Int>.ReadOnly Positions;

            [ReadOnly] public RectInt rect0;
            [ReadOnly] public RectInt rect1;
            [ReadOnly] public RectInt rect2;

            [ReadOnly] public int spaceBoxEndX;
            [ReadOnly] public NativeList<SpaceBox> gameSpaceBoxes;

            [WriteOnly] public NativeQueue<SpaceLodBox> OutputBoxesLoad;
            [WriteOnly] public NativeQueue<SpaceLodBox> OutputBoxesUnLoad;
            [WriteOnly] public NativeList<SpaceBox> OutputBoxesUpdate;

            public void Execute(int index)
            {
                var pos = Positions[index];
                int i = pos.x;
                int j = pos.y;

                int idx = i * spaceBoxEndX + j;
                var box = gameSpaceBoxes[idx];

                var ikey = new Vector2Int(i, j);

                if (rect0.Contains(ikey))
                {
                    if (box.HasLoaded) //已加载的
                    {
                        if (box.LodLevel == 0)
                            return;
                        if (box.LodLevel != 0) //需要卸载
                        {
                            UnLoadMark(index, box.LodLevel, ref box);
                        }
                    }

                    LoadMark(index, 0, ref box);
                }
                else if (rect1.Contains(ikey))
                {
                    if (box.HasLoaded) //已加载的
                    {
                        if (box.LodLevel == 1)
                            return;
                        if (box.LodLevel != 1) //需要卸载
                        {
                            UnLoadMark(index, box.LodLevel, ref box);
                        }
                    }

                    LoadMark(index, 1, ref box);
                }
                else if (rect2.Contains(ikey))
                {
                    if (box.HasLoaded) //已加载的
                    {
                        if (box.LodLevel == 2)
                            return;
                        if (box.LodLevel != 2) //需要卸载
                        {
                            UnLoadMark(index, box.LodLevel, ref box);
                        }
                    }

                    LoadMark(index, 2, ref box);
                }
                else
                {
                    if (box.HasLoaded) //已加载的
                    {
                        UnLoadMark(index, box.LodLevel, ref box);
                    }
                }
            }

            private void UnLoadMark(int index, int lod, ref SpaceBox box)
            {
                SpaceLodBox lodBox = new SpaceLodBox
                {
                    Flag = true,
                    BoxId = index,
                    Lod = lod,
                    Bound = box.Bound,
                    Weight = 0,
                };
                switch (box.LodLevel)
                {
                    case 0:
                        box.HasLoaded = false;
                        box.LodLevel = -1;
                        break;
                }

                OutputBoxesUpdate[index] = box;
                OutputBoxesUnLoad.Enqueue(lodBox);
            }

            private void LoadMark(int index, int lod, ref SpaceBox box)
            {
                SpaceLodBox lodBox = new SpaceLodBox
                {
                    Flag = true,
                    BoxId = index,
                    Lod = lod,
                    Bound = box.Bound,
                    Weight = 0,
                };
                switch (box.LodLevel)
                {
                    case 0:
                        if (box.Lod0.Length == 0)
                            return;
                        box.HasLoaded = true;
                        box.LodLevel = lod;
                        break;
                }

                OutputBoxesUpdate[index] = box;
                OutputBoxesLoad.Enqueue(lodBox);
            }
        }
        
        [BurstCompile]
        public struct ProcessLodJob : IJobParallelFor
        {
            [ReadOnly] public NativeArray<SpaceLodBox> SpaceBoxes;
            [WriteOnly] public NativeParallelMultiHashMap<int, int>.ParallelWriter OutputKeys;

            public void Execute(int index)
            {
                //for (int i = 0; i < SpaceBoxes.Length; i++)
                {
                    var box = SpaceBoxes[index];
                    if (box.Assets.Length == 0)
                        return;
                    var weight = box.Weight == 100 ? 10000 : 0; //如果权重为100做特殊标识
                    switch (box.Lod)
                    {
                        case 0:
                            foreach (var asset in box.Assets)
                                OutputKeys.Add(asset, 0 + weight);
                            break;
                        case 1:
                            foreach (var asset in box.Assets)
                                OutputKeys.Add(asset, 1 + weight);
                            break;
                        case 2:
                            foreach (var asset in box.Assets)
                                OutputKeys.Add(asset, 2 + weight);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }
            }
        }

        [BurstCompile]
        public struct ProcessViewBoundJob : IJobParallelFor
        {
            [ReadOnly] public NativeArray<SpaceLodBox> SpaceBoxes;

            //[WriteOnly] public NativeParallelHashMap<int,SpaceLodBox> OutputKeys; //key 不会重复 
            [WriteOnly] public NativeQueue<SpaceLodBox> OutputQueue;
            [ReadOnly] public Frustum CurrentFrustum;

            public void Execute(int index)
            {
                var box = SpaceBoxes[index];

                // 1. 判断包围盒是否与视锥体相交
                bool isVisible = CurrentFrustum.Intersects(box.Bound);

                // 2. 更新权重或 LOD 状态
                if (isVisible)
                {
                    box.Weight = 100; // 视锥内权重 100
                }

                // 3. 写出更新后的 box
                //OutputKeys[box.BoxId] = box;
                OutputQueue.Enqueue(box);
            }
        }

        public struct ProcessFindJob : IJobParallelFor
        {
            [ReadOnly] public NativeArray<SpaceAsset> InputKeys;

            public void Execute(int index)
            {
                throw new NotImplementedException();
            }
        }
    }

    public struct SpaceAsset
    {
        public int SceneObjKey; //场景中的对象   指向资源中的对象引用（SpaceAssetRef）
        public int AssetObjKey; //资源中的对象   指向资源中的数据引用 (SpaceAssetDataRef)
        public Bounds Bound;    //资源包围盒，   确定资源归属于哪些box
    }

    [Serializable]
    public class SpaceAssetRef
    {
        public int SceneObjKey; //场景中的对象
        public MeshFilter[] Lod0Filter;
        public MeshRenderer[] Lod0Renderer;
        public MeshFilter[] Lod1Filter;
        public MeshRenderer[] Lod1Renderer;
        public MeshFilter[] Lod2Filter;
        public MeshRenderer[] Lod2Renderer;
    }

    public class SpaceAssetDataRef
    {
        public int AssetObjKey;
        public string[] Lod0AllAssets;
        public string[] Lod1AllAssets;
        public string[] Lod2AllAssets;
    }

    //空间盒子 50*50
    public struct SpaceBox
    {
        private static int _x = 0;
        public static int _z = 0;
        public const int SpaceBoxSize = 15;
        public static int SpaceBoxStartX => _x;
        public static int SpaceBoxEndX => _z;
        public static int SpaceBoxStartZ => 0;
        public static int SpaceBoxEndZ => 100;
        public static int Lod2BoxCount => 20;
        public static int Lod1BoxCount => 10;
        public static int Lod0BoxCount => 5;

        public static void Init(int start, int end)
        {
            _x = start;
            _z = end;
        }

        public static RectInt GetSpaceLodBox(int lodIndex, int sx, int sz)
        {
            int count = 0;
            switch (lodIndex)
            {
                case 0:
                    count = Lod0BoxCount;
                    break;
                case 1:
                    count = Lod1BoxCount;
                    break;
                case 2:
                    count = Lod2BoxCount;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            int startX = Mathf.Max(sx - count, SpaceBoxStartX);
            int endX = Mathf.Min(sx + count, SpaceBoxEndX);
            int startZ = Mathf.Max(sz - count, SpaceBoxStartZ);
            int endZ = Mathf.Min(sz + count, SpaceBoxEndZ);
            var b = new RectInt
            {
                xMin = startX,
                xMax = endX,
                yMin = startZ,
                yMax = endZ
            };
            return b;
        }

        public NativeList<int> Lod0;
        public NativeList<int> Lod1;
        public NativeList<int> Lod2;

        public int BoxId;
        public Vector2Int Center;
        public int Weight; //权重 默认权重为0， 视锥体内权重+10
        public int LodLevel;
        public Bounds Bound;
        public bool HasLoaded;
    }

    [BurstCompile]
    public struct SpaceLodBox
    {
        public bool Flag; //标识是否有数据
        public int Lod;
        public int BoxId;
        public int Weight;
        public Bounds Bound;

        public NativeList<int> Assets;
    }

    public struct SpaceAssetLoadRef
    {
        public int SceneObjIndex; //加载对象
        public int LodIndex; //lod
        public int RefCount; //加载次数
        public int Weight;
    }

    public class LoadManager : MonoBehaviour
    {
        public static LoadManager Inst { get; private set; }

        public struct LoadItem
        {
            public int SceneObjKey; //场景中的对象
            public int Lod;
            public int LoadCount;
            public int Weight;
        }

        public readonly LinkedList<LoadItem> LoadList = new LinkedList<LoadItem>();
        public readonly LinkedList<LoadItem> UnLoadList = new LinkedList<LoadItem>();
        private readonly Dictionary<Vector2Int,int> _loadCountDict = new Dictionary<Vector2Int, int>();
        public void MarkLoad(SpaceAssetLoadRef assetRef)
        {
            if (LoadList.Count == 0)
            {
                LoadList.AddLast(new LoadItem()
                {
                    SceneObjKey = assetRef.SceneObjIndex,
                    LoadCount = assetRef.RefCount,
                    Weight = assetRef.Weight,
                });
                return;
            }

            switch (assetRef.Weight)
            {
                case 100:
                    LoadList.AddFirst(new LoadItem()
                    {
                        SceneObjKey = assetRef.SceneObjIndex,
                        Lod = assetRef.LodIndex,
                        LoadCount = assetRef.RefCount,
                        Weight = assetRef.Weight,
                    });
                    break;
                default:
                    LoadList.AddLast(new LoadItem()
                    {
                        SceneObjKey = assetRef.SceneObjIndex,
                        Lod = assetRef.LodIndex,
                        LoadCount = assetRef.RefCount,
                        Weight = assetRef.Weight,
                    });
                    break;
            }
        }

        private void Update()
        {
            while (LoadList.Count != 0)
            {
                var first = LoadList.First;
                LoadList.RemoveFirst();
                //加载资源 加载次数

                var key = first.Value.SceneObjKey;
                //var assetObj = GameSpaceAssets.Inst.Assets[key];
                // var sceneObj = GameSpaceAssets.Inst.RefAssetsInSpace[assetObj.SceneObjKey];
                // var assetData = GameSpaceAssets.Inst.AssetsDataInSpace[assetObj.AssetObjKey];
                var sceneObj = GameSpaceAssets.Inst.RefAssetsInSpace[key];
                var assetData = GameSpaceAssets.Inst.AssetsDataInSpace[key];
                var loadKey = new Vector2Int(key, first.Value.Lod);
                _loadCountDict[loadKey] = _loadCountDict.TryGetValue(loadKey, out var value) ? value + first.Value.LoadCount : first.Value.LoadCount;
                switch (first.Value.Lod)
                {
                    case 0:
                        SceneAssetLoadManager.LoadAsset(assetData.Lod0AllAssets,sceneObj.Lod0Filter,sceneObj.Lod0Renderer);
                        break;
                    case 1:
                        SceneAssetLoadManager.LoadAsset(assetData.Lod1AllAssets,sceneObj.Lod1Filter,sceneObj.Lod1Renderer);
                        break;
                    case 2:
                        SceneAssetLoadManager.LoadAsset(assetData.Lod2AllAssets,sceneObj.Lod2Filter,sceneObj.Lod2Renderer);
                        break;
                }
            }

            while (UnLoadList.Count != 0)
            {
                var first = UnLoadList.First;
                UnLoadList.RemoveFirst();
            }
        }
    }

    public static class SceneAssetLoadManager
    {
        struct BinderItem
        {
            public MeshFilter Filter;
            public MeshRenderer Render;
            public string AssetName;
        }
        static Dictionary<string,Object> _assetCache = new Dictionary<string, Object>();

        private static Queue<BinderItem> _binderQueue = new Queue<BinderItem>(1000);

        private static Queue<string> _loadQueue = new Queue<string>(1000);
        //static 
        public static void LoadAsset(string[] assetIds,MeshFilter[] filters, MeshRenderer[] renderers)
        {
            if(assetIds.Length != filters.Length + renderers.Length)
                throw new System.Exception("assetIds.Length != filters.Length + renderers.Length");
            for (int i = 0; i < assetIds.Length; i++)
            {
                var assetName = assetIds[i];
                var binder = new BinderItem()
                {
                    Filter = i > filters.Length ? null : filters[i],
                    Render = i > filters.Length ? renderers[i - filters.Length] : null,
                    AssetName = assetName
                };
                _binderQueue.Enqueue(binder);
                if (_assetCache.ContainsKey(assetName))
                    continue;
                _assetCache[assetName] = null;
                _loadQueue.Enqueue(assetName);
            }
            foreach (var assetName in assetIds)
            {
                if (_assetCache.ContainsKey(assetName))
                    continue;
                _binderQueue.Enqueue(new BinderItem(){});
            }
        }
        
        public static void Update()
        {
            {
                int loadCount = 10;
                while (_loadQueue.Count > 0)
                {
                    if (--loadCount == 0)
                        break;
                    var assetName = _loadQueue.Dequeue();
                    LoadAsset(assetName, asset => _assetCache[assetName] = asset);
                }
            }

            {
                var count = _binderQueue.Count;
                while (_binderQueue.Count > 0)
                {
                    if (count == 0)
                        break;
                    var item = _binderQueue.Dequeue();
                    count--;
                    if (_assetCache.TryGetValue(item.AssetName, out var asset) && asset != null)
                    {
                        if (item.Filter != null)
                        {
                            item.Filter.sharedMesh = asset as Mesh;
                        }

                        if (item.Render != null)
                        {
                            item.Render.sharedMaterial = asset as Material;
                        }
                    }
                    else
                    {
                        _binderQueue.Enqueue(item);
                    }
                }
            }
        }

        static void LoadAsset(string assetName, Action<UnityEngine.Object> callback)
        {
#if UNITY_EDITOR
            var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(assetName);
            callback(asset);
#else
#endif
        }
    }

    public static class SceneViewCenter
    {
        public static Camera ViewCamera; // 主相机引用
        public static Frustum CurrentFrustum; // 预先计算好的视锥体
    }

    [Serializable, BurstCompile]
    public struct Frustum
    {
        public Plane[] planes; // 六个平面：左、右、上、下、近、远

        public Frustum(Camera camera)
        {
            planes = GeometryUtility.CalculateFrustumPlanes(camera);
        }

        public bool Intersects(Bounds bounds)
        {
            for (int i = 0; i < 6; i++)
            {
                if (planes[i].distance <= -bounds.extents.x * Mathf.Abs(planes[i].normal.x) -
                    bounds.extents.y * Mathf.Abs(planes[i].normal.y) -
                    bounds.extents.z * Mathf.Abs(planes[i].normal.z))
                {
                    return false;
                }
            }

            return true;
        }
    }
}